package com.springboot.base.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.springboot.base.common.BusinessException;
import com.springboot.base.common.EmBusinessError;
import com.springboot.base.dal.CategoryMapper;
import com.springboot.base.dal.ShopMapper;
import com.springboot.base.model.Category;
import com.springboot.base.model.Seller;
import com.springboot.base.model.Shop;
import com.springboot.base.service.ICategoryService;
import com.springboot.base.service.ISellerService;
import com.springboot.base.service.IShopService;
import jdk.nashorn.internal.scripts.JS;
import lombok.extern.java.Log;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.util.MapUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author meiguangya
 * @since 2020-07-17
 */
@Service
public class ShopServiceImpl implements IShopService {

    @Autowired
    private ISellerService sellerService;

    @Autowired
    private ICategoryService categoryService;

    @Autowired
    private ShopMapper shopMapper;

    @Autowired
    private RestHighLevelClient highLevelClient;

    private final String INDEX_NAME = "shop";

    @Override
    @Transactional
    public Shop create(Shop shop) throws BusinessException {
        shop.setCreatedAt(new Date());
        shop.setUpdatedAt(new Date());

        //校验商家是否存在
        Seller seller = sellerService.getById(shop.getSellerId());
        if (seller == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商户不存在");
        }

        if (seller.getDisabledFlag() == 1) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商户已禁用");
        }

        Category category = categoryService.getById(shop.getCategoryId());
        if (category == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "品类不存在");
        }
        shopMapper.insert(shop);

        return shopMapper.selectById(shop.getId());
    }

    @Override
    public Shop get(Integer id) {
        Shop shop = shopMapper.selectById(id);
        if (shop == null) {
            return null;
        }
        shop.setCategory(categoryService.getById(shop.getCategoryId()));
        shop.setSeller(sellerService.getById(shop.getSellerId()));

        return shop;
    }

    @Override
    public IPage<Shop> qryListByPage(Page<Shop> page) {
        IPage<Shop> list = shopMapper.selectByPage(page);
        List<Shop> shops = list.getRecords();
        shops.forEach(shop -> {
            shop.setCategory(categoryService.getById(shop.getCategoryId()));
            shop.setSeller(sellerService.getById(shop.getSellerId()));
        });
        list.setRecords(shops);
        return list;
    }

    @Override
    public int countAllShop() {
        return shopMapper.selectCount(null);
    }

    @Override
    public List<Shop> recommend(BigDecimal longitude, BigDecimal latitude) {
        List<Shop> shopList = shopMapper.recommend(longitude, latitude);
        return shopList;
    }

    @Override
    public List<Shop> search(BigDecimal longitude, BigDecimal latitude, String keyword, Integer orderby, Integer categoryId, String tags) {
        List<Shop> shopList = shopMapper.search(longitude, latitude, keyword, orderby, categoryId, tags);
        return shopList;
    }

    @Override
    public Map<String, Object> searchByEsHighLeverl(BigDecimal longitude, BigDecimal latitude,
                                                    String keyword, Integer orderby,
                                                    Integer categoryId, String tags) throws IOException {
        Map<String, Object> result = new HashMap<>();

        SearchRequest req = new SearchRequest("shop");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchQuery("name", keyword));
        builder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        req.source(builder);

        SearchResponse rep = highLevelClient.search(req, RequestOptions.DEFAULT);
        SearchHit[] hits = rep.getHits().getHits();
        List<Integer> shopIds = new ArrayList<>();
        for (SearchHit hit : hits) {
            int id = (Integer) hit.getSourceAsMap().get("id");
            shopIds.add(id);
        }

        List<Shop> shops = shopIds.stream().map(id -> {
            return shopMapper.selectById(id);
        }).collect(Collectors.toList());

        result.put("shop", shops);
        return result;
    }

    @Override
    public Map<String, Object> searchByEsLowerLeverl(BigDecimal longitude, BigDecimal latitude, String keyword, Integer orderby, Integer categoryId, String tags) throws IOException {

        //使用低级Client实现复杂查询 面向对象的方法构造Json
        JSONObject reqJsonObj = buildQueryShopObj(longitude, latitude, keyword, orderby, categoryId, tags);
        final String reqJson = reqJsonObj.toString();
        Request request = new Request(HttpGet.METHOD_NAME, "/" + INDEX_NAME + "/_search");
        request.setJsonEntity(reqJson);
        Response response = highLevelClient.getLowLevelClient().performRequest(request);

        List<Shop> shops = new ArrayList<>();
        String repStr = EntityUtils.toString(response.getEntity());
        JSONObject jsonObject = JSONObject.parseObject(repStr);
        JSONArray hits = jsonObject.getJSONObject("hits").getJSONArray("hits");
        for (int i = 0; i < hits.size(); i++) {
            JSONObject o = hits.getJSONObject(i);
            Integer id = new Integer(o.get("_id").toString());
            BigDecimal distance = new BigDecimal(o.getJSONObject("fields").getJSONArray("distance").get(0).toString());

            Shop shop = get(id);
            shop.setDistance(distance.multiply(new BigDecimal(1000)).setScale(0, BigDecimal.ROUND_CEILING).intValue());
            shops.add(shop);
        }

        JSONArray tagArrays = jsonObject.getJSONObject("aggregations").getJSONObject("group_by_tags").getJSONArray("buckets");
        List<Map> tagList = new ArrayList<>();
        for (int i = 0; i < tagArrays.size(); i++) {
            JSONObject o = tagArrays.getJSONObject(i);
            Map<String,Object> tagMap = new HashMap<>();
            tagMap.put("tags",o.getString("key"));
            tagMap.put("num",o.getInteger("doc_count"));
            tagList.add(tagMap);
        }

        //返回
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("shop", shops);
        resultMap.put("tags", tagList);
        return resultMap;
    }

    private JSONObject buildQueryShopObj(BigDecimal longitude, BigDecimal latitude, String keyword, Integer orderby, Integer categoryId, String tags) {
        JSONObject queryObj = new JSONObject();
        queryObj.put("_source", "*");
        //script_fields
        JSONObject scriptFields = new JSONObject();
        scriptFields.put("distance", new JSONObject());
        scriptFields.getJSONObject("distance").put("script", new JSONObject());
        scriptFields.getJSONObject("distance").getJSONObject("script").put("source", "haversin(lat,lon,doc['location'].lat,doc['location'].lon)");
        scriptFields.getJSONObject("distance").getJSONObject("script").put("lang", "expression");
        scriptFields.getJSONObject("distance").getJSONObject("script").put("params", new JSONObject());
        scriptFields.getJSONObject("distance").getJSONObject("script").getJSONObject("params").put("lat", latitude);
        scriptFields.getJSONObject("distance").getJSONObject("script").getJSONObject("params").put("lon", longitude);

        queryObj.put("script_fields", scriptFields);

        //query
        JSONObject query = new JSONObject();
        //function_score
        query.put("function_score", new JSONObject());
        query.getJSONObject("function_score").put("query", new JSONObject());
        query.getJSONObject("function_score").getJSONObject("query").put("bool", new JSONObject());

        //must 召回条件
        final JSONArray mustArray = new JSONArray();
        //1.name
        JSONObject nameObj = new JSONObject();
        nameObj.put("match", new JSONObject());
        nameObj.getJSONObject("match").put("name", new JSONObject());
        nameObj.getJSONObject("match").getJSONObject("name").put("query", keyword);
        nameObj.getJSONObject("match").getJSONObject("name").put("boost", 0.2);
        mustArray.add(nameObj);
        //2.term
        JSONObject termObj = new JSONObject();
        termObj.put("term", new JSONObject());
        termObj.getJSONObject("term").put("seller_disabled_flag", 0);
        mustArray.add(termObj);

        //3.category
        if (categoryId != null) {
            //若用户选中了类目
            final JSONObject category = new JSONObject();
            category.put("term", new JSONObject());
            category.getJSONObject("term").put("category_id", categoryId);
            mustArray.add(category);
        }


        //4.tags
        if (tags != null) {
            //若用户选中了标签
            final JSONObject tag = new JSONObject();
            tag.put("term", new JSONObject());
            tag.getJSONObject("term").put("tags", tags);
            mustArray.add(tag);
        }

        query.getJSONObject("function_score").getJSONObject("query").getJSONObject("bool").put("must", mustArray);
        JSONArray functions = new JSONArray();
        //functions
        if (orderby == null) {
            //1.gauss
            JSONObject gauss = new JSONObject();
            gauss.put("gauss", new JSONObject());
            gauss.getJSONObject("gauss").put("location", new JSONObject());
            gauss.getJSONObject("gauss").getJSONObject("location").put("origin", latitude + "," + longitude);
            gauss.getJSONObject("gauss").getJSONObject("location").put("scale", "100km");
            gauss.getJSONObject("gauss").getJSONObject("location").put("offset", "0km");
            gauss.getJSONObject("gauss").getJSONObject("location").put("decay", "0.5");
            gauss.put("weight", 100);
            functions.add(gauss);
        } else {
            //低价排序
            JSONObject orderBy = new JSONObject();
            orderBy.put("field_value_factor", new JSONObject());
            orderBy.getJSONObject("field_value_factor").put("field", "price_per_man");
            orderBy.put("weight", 1);
            functions.add(orderBy);
        }


        //2.remark_sorce
//        JSONObject remarkSorce = new JSONObject();
//        remarkSorce.put("field_value_factor", new JSONObject());
//        remarkSorce.getJSONObject("field_value_factor").put("field", "remark_score");
//        remarkSorce.put("weight", 0.002);
//        functions.add(remarkSorce);


        //3.seller_remark_score
//        JSONObject sellerRemarkScore = new JSONObject();
//        sellerRemarkScore.put("field_value_factor", new JSONObject());
//        sellerRemarkScore.getJSONObject("field_value_factor").put("field", "seller_remark_score");
//        sellerRemarkScore.put("weight", 0.01);
//        functions.add(sellerRemarkScore);

        query.getJSONObject("function_score").put("functions", functions);
        //mode
        query.getJSONObject("function_score").put("score_mode", "sum");
        query.getJSONObject("function_score").put("boost_mode", "replace");

        //put query
        queryObj.put("query", query);

        //sort
        JSONObject sortObject = new JSONObject();
        JSONArray sortArray = new JSONArray();
        JSONObject scoreObj = new JSONObject();
        scoreObj.put("_score", new JSONObject());
        if (orderby == null) {
            scoreObj.getJSONObject("_score").put("order", "desc");
        } else {
            scoreObj.getJSONObject("_score").put("order", "asc");
        }
        sortArray.add(scoreObj);
        sortObject.put("sort", sortArray);
        //put sort
        queryObj.put("sort", sortArray);

        //加入使用标签聚合
        queryObj.put("aggs", new JSONObject());
        queryObj.getJSONObject("aggs").put("group_by_tags", new JSONObject());
        queryObj.getJSONObject("aggs").getJSONObject("group_by_tags").put("terms", new JSONObject());
        queryObj.getJSONObject("aggs").getJSONObject("group_by_tags").getJSONObject("terms").put("field", "tags");


        return queryObj;
    }


    @Override
    public List<Map<String, Object>> searchGroupByTags(String keyword, Integer categoryId, String tags) {
        List<Map<String, Object>> result = shopMapper.searchGroupByTags(keyword, categoryId, tags);
        return result;
    }


    /**
     * 构造分词识别器 增加搜索相关性
     */
    private Map<String, Object> analyzeCategoryKeyword(String keyword) throws IOException {
        Map<String, Object> result = new HashMap<>();
        Request request = new Request("GET", "/_analyze");
        request.setJsonEntity("");
        Response response = highLevelClient.getLowLevelClient().performRequest(request);
        String responseStr = EntityUtils.toString(response.getEntity());

        JSONObject responseJSON = JSONObject.parseObject(responseStr);
        JSONArray array = responseJSON.getJSONArray("tokens");
        for (int i = 0; i < array.size(); i++) {
            String token = array.getJSONObject(i).get("token").toString();
            Integer categoryId = getCategoryIdByToken(token);
            if (categoryId != null) {
                result.put(token, categoryId);
            }
        }
        return result;
    }


    private Integer getCategoryIdByToken(String token) {
        for (Integer i : categoryWorkMap.keySet()) {
            if (categoryWorkMap.get(i).contains(token)) {
                return i;
            }
        }
        return null;
    }


    private Map<Integer, List<String>> categoryWorkMap = new HashMap<>();

    @PostConstruct
    public void init() {
        //美食
        categoryWorkMap.put(1, new ArrayList<>());
        //酒店
        categoryWorkMap.put(2, new ArrayList<>());

        categoryWorkMap.get(1).add("吃饭");
        categoryWorkMap.get(1).add("下午茶");

        categoryWorkMap.get(2).add("住宿");
        categoryWorkMap.get(2).add("休息");
        categoryWorkMap.get(2).add("睡觉");
    }

}
